﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Collections;
using System.Net;
using System.ComponentModel;
using ModbusTCP;


namespace AppMain
{
    class PlcComm : System.Windows.Forms.Form
    {
        //PLC Address: C0-C1777 = 3072-4095
        //  Used for control: C300 = 3264
        //Offset is 32 for TX6 coils and 48 for TX7 coils
        private ushort P_PlcAddress_TX6Heartbeat = 3264;
        private ushort P_PlcAddress_TX7Heartbeat = 3269;
        private ushort P_PlcAddress_TX6Error = 3265;
        private ushort P_PlcAddress_TX6HeatingOn = 3266;
        private ushort P_PlcAddress_TX7Error = 3267;
        private ushort P_PlcAddress_TX7HeatingOn = 3268;
        private ushort P_PlcAddress_TX6 = 3296;
        private ushort P_PlcAddress_TX7 = 3312;

        private bool[] g_PlcReadData_b = new bool[16];
        private bool[] g_PlcOverrideData_b = new bool[16];
        private bool g_UserOverridesBurk_b = false;

        /*****************************************************************
         * Memory Mapping between Plc data and intended instructions     *
         ****************************************************************/

        public enum B_Write
        {
            Heartbeat=0,
            ErrorTx6,
            HeatingOnTx6,
            ErrorTx7,
            HeatingOnTx7,
        }
        private enum B_TxRead
        {
            Ch1=0,
            Ch2,
            Ch4,
            Ch8,
            Pwr1,
            Pwr2,
            Mod1,
            Mod2,
            HeaterOn,
            GeneralOn,
            GeneratorModulation,
            GeneratorPower,
            HeatOnForMinimumTime
        }
        
        //Modbus variables
        public bool CommEnabled { get; set; }
        public string PlcIpAddress { get; set; }
        public int TxNum { get; set; }

        
        private ModbusTCP.Master MBmaster;
        private bool ModbusCommThreadOpen_b = false;
        private bool ModbusReadPending_b;
        private ushort C_ReadID = 1;
        private ushort C_WriteID = 2;
        private bool Heartbeat;
        

        //Create instance of log data for logging from this file
        AppMain.LogData log = new AppMain.LogData();

        public void ReadWrite()
        {
            //Open Modbus if not already connected
            if (ModbusCommThreadOpen_b == false)
            {
                connect();
            }
            else
            {
                //Ready to communicate with PLC

                /***************************************************/
                /*  Plc Modbus Functions                           */
                /***************************************************/
                //Toggle Heartbeat bit
                Heartbeat = !Heartbeat;
                if (TxNum == 6) MBmaster.WriteSingleCoils(C_WriteID, P_PlcAddress_TX6Heartbeat, Heartbeat);
                if (TxNum == 7) MBmaster.WriteSingleCoils(C_WriteID, P_PlcAddress_TX7Heartbeat, Heartbeat);

                
                //Write Coils
                //Convert bit array into byte array
                if (g_UserOverridesBurk_b == true)
                {
                    var l_PlcWriteBytes = new byte[3];
                    BitArray bitArray2 = new BitArray(g_PlcOverrideData_b);
                    bitArray2.CopyTo(l_PlcWriteBytes, 0);
                    
                    //Write Coils using Modbus
                    if (TxNum == 6) MBmaster.WriteMultipleCoils(C_WriteID, P_PlcAddress_TX6, 16, l_PlcWriteBytes);
                    if (TxNum == 7) MBmaster.WriteMultipleCoils(C_WriteID, P_PlcAddress_TX7, 16, l_PlcWriteBytes);

                    //reset override flag
                    g_UserOverridesBurk_b = false;
                }
                
                //Read Coils
                ushort Length = (ushort)g_PlcReadData_b.Length;

                if (TxNum == 6) MBmaster.ReadCoils(C_ReadID, P_PlcAddress_TX6, Length);
                if (TxNum == 7) MBmaster.ReadCoils(C_ReadID, P_PlcAddress_TX7, Length);
                //ModbusReadPending_b = true; //Request is sent; new data is pending
                
            }
        }

        public void WriteToPlc(B_Write bit,bool input)
        {
            /************************************
             * Public function to write data to PLC
             ************************************/
            if (TxNum == 6)
            {
                if (bit == B_Write.ErrorTx6) MBmaster.WriteSingleCoils(C_WriteID, P_PlcAddress_TX6Error, input);
                if (bit == B_Write.HeatingOnTx6) MBmaster.WriteSingleCoils(C_WriteID, P_PlcAddress_TX6HeatingOn, input);
            }
            if (TxNum == 7)
            {
                if (bit == B_Write.ErrorTx7) MBmaster.WriteSingleCoils(C_WriteID, P_PlcAddress_TX7Error, input);
                if (bit == B_Write.HeatingOnTx7) MBmaster.WriteSingleCoils(C_WriteID, P_PlcAddress_TX7HeatingOn, input);
            }
        }

        public RequestToTx TxRequest()
        {
            if (ModbusReadPending_b == false)
            {
                return convertRequest();
            }
            else
            {
                //Data doesn't exist, or new data is coming soon
                return null;
            } 
        }

        private RequestToTx convertRequest()
        {
            
            //Private function that returns the current request for the selected transmitter
            RequestToTx request = new RequestToTx();

            //Set Channel
            request.Channel = 0;
            if (g_PlcReadData_b[Convert.ToInt32(B_TxRead.Ch1)])
                request.Channel += 1;
            if (g_PlcReadData_b[Convert.ToInt32(B_TxRead.Ch2)])
                request.Channel += 2;
            if (g_PlcReadData_b[Convert.ToInt32(B_TxRead.Ch4)])
                request.Channel += 4;
            if (g_PlcReadData_b[Convert.ToInt32(B_TxRead.Ch8)])
                request.Channel += 8;

           /***************************************
             * !!!Command!!!
             * truth table (PLC Input Bits / Thompson Output Request):
             * b0 = Input HeaterOn
             * b1 = Input GeneralOn
             * | b1 | b0 |  Command
             * |----|----|-----------------
             * | 0  |  0 | GeneralShutdown
             * | 0  |  1 | HeatingOn
             * | 1  |  0 | CircuitBreakerOn
             * | 1  |  1 | GeneralOn
             ****************************************/
            int command = 0;
            if (g_PlcReadData_b[Convert.ToInt32(B_TxRead.HeaterOn)])
                command+=1;
            if (g_PlcReadData_b[Convert.ToInt32(B_TxRead.GeneralOn)])
                command+=2;
            switch (command)
	        {
                case 0:
                    request.Command = Thompson.Tx.E_CommandTx.GeneralShutdown;
                    break;
                case 1:
                    request.Command = Thompson.Tx.E_CommandTx.HeatingOn;
                    break;
                case 2:
                    request.Command = Thompson.Tx.E_CommandTx.CircuitBreakerOn;
                    break;
                case 3:
                    request.Command = Thompson.Tx.E_CommandTx.GeneralOn;
                    break;
		        default:
                    log.Write("Error in interpreting PLC instuction! (PlcComm: convertRequest Command)");
                    CommEnabled = false;
                break;
	        }

            //Set Modulation
            int modulation = 0;
            if (g_PlcReadData_b[Convert.ToInt32(B_TxRead.Mod1)])
                modulation += 1;
            if (g_PlcReadData_b[Convert.ToInt32(B_TxRead.Mod2)])
                modulation += 2;
            request.ModulationRequest = modulation;
            
            //Set Power
            int power = 0;
            if (g_PlcReadData_b[Convert.ToInt32(B_TxRead.Pwr1)])
                power += 1;
            if (g_PlcReadData_b[Convert.ToInt32(B_TxRead.Pwr2)])
                power += 2;
            request.PowerRequest = power;

            //Set Modulation for Generator
            request.GenModulation = g_PlcReadData_b[Convert.ToInt32(B_TxRead.GeneratorModulation)];

            //Set Power for Generator
            request.GenPower = g_PlcReadData_b[Convert.ToInt32(B_TxRead.GeneratorPower)];

            //Set Heat On For Minimum Time
            request.HeatOnForMinTime = g_PlcReadData_b[Convert.ToInt32(B_TxRead.HeatOnForMinimumTime)];

            return request;
        }
        
        public void OverrideBurk(RequestToTx NewTxReq)
        {
            //Function to copy a TX request to data to be copied to PLC
            convertRequestToPlc(NewTxReq);

            //Request to write new data to PLC
            g_UserOverridesBurk_b = true;
        }

        private void convertRequestToPlc(RequestToTx request)
        {
            
            //Convert Channel request
            g_PlcOverrideData_b[Convert.ToInt32(B_TxRead.Ch1)] = Convert.ToBoolean(request.Channel & 1);
            g_PlcOverrideData_b[Convert.ToInt32(B_TxRead.Ch2)] = Convert.ToBoolean(request.Channel & 2);
            g_PlcOverrideData_b[Convert.ToInt32(B_TxRead.Ch4)] = Convert.ToBoolean(request.Channel & 4);
            g_PlcOverrideData_b[Convert.ToInt32(B_TxRead.Ch8)] = Convert.ToBoolean(request.Channel & 8);

            //Convert Modulation Request
            g_PlcOverrideData_b[Convert.ToInt32(B_TxRead.Mod1)] = Convert.ToBoolean(request.ModulationRequest & 1);
            g_PlcOverrideData_b[Convert.ToInt32(B_TxRead.Mod2)] = Convert.ToBoolean(request.ModulationRequest & 2);

            //Convert Power Request
            g_PlcOverrideData_b[Convert.ToInt32(B_TxRead.Pwr1)] = Convert.ToBoolean(request.PowerRequest & 0x01);
            g_PlcOverrideData_b[Convert.ToInt32(B_TxRead.Pwr2)] = Convert.ToBoolean(request.PowerRequest & 0x02);


            //Convert CommandTx Request
            bool heater = false;
            bool general = false;
            switch (request.Command)
            {
                case Thompson.Tx.E_CommandTx.GeneralShutdown:
                    heater = false;
                    general = false;
                    break;
                case Thompson.Tx.E_CommandTx.HeatingOn:
                    heater = true;
                    general = false;
                    break;
                case Thompson.Tx.E_CommandTx.CircuitBreakerOn:
                    heater = false;
                    general = true;
                    break;
                case Thompson.Tx.E_CommandTx.GeneralOn:
                    heater = true;
                    general = true;
                    break;
                default:
                    break;
            }
            g_PlcOverrideData_b[Convert.ToInt32(B_TxRead.HeaterOn)] = heater;
            g_PlcOverrideData_b[Convert.ToInt32(B_TxRead.GeneralOn)] = general;

        }
        
        private void connect()
        {
            log.Write(string.Format("Attempting to connect to PLC: {0}",PlcIpAddress));
            try
            {
                // Create new modbus master and add event functions
                MBmaster = new Master(PlcIpAddress, 502);
                MBmaster.OnResponseData += new ModbusTCP.Master.ResponseData(MBmaster_OnResponseData);
                MBmaster.OnException += new Master.ExceptionData(MBmaster_OnException);
                // Show additional fields, enable watchdog


                //Start continuous communication
                ModbusCommThreadOpen_b = true;
                ModbusReadPending_b = true; //Request is sent; new data is pending
                log.Write(string.Format("Connection success."));

            }
            catch (SystemException error)
            {
                log.Write(string.Format("Error connecting to PLC! {0}", error.Message));
                ModbusCommThreadOpen_b = false;
                CommEnabled = false;
            }
        }

        // ------------------------------------------------------------------------
        // Event for response data
        // ------------------------------------------------------------------------
        private void MBmaster_OnResponseData(ushort ID, byte function, byte[] values)
        {
            // ------------------------------------------------------------------
            // Seperate calling threads
            if (this.InvokeRequired)
            {
                this.BeginInvoke(new ModbusTCP.Master.ResponseData(MBmaster_OnResponseData), new object[] { ID, function, values });
                return;
            }

            // Ignore watchdog response data
            if (ID == 0xFF) return;

            if (ID == C_ReadID)
            {

                /***************************************************/
                /*  Plc Read & Display data                        */
                /***************************************************/
                BitArray PlcRxData = new BitArray(values);
                PlcRxData.CopyTo(g_PlcReadData_b, 0);
                ModbusReadPending_b = false;

                

            }
        }

        // ------------------------------------------------------------------------
        // Modbus TCP slave exception
        // ------------------------------------------------------------------------
        private void MBmaster_OnException(ushort id, byte function, byte exception)
        {
            // ------------------------------------------------------------------
            // Seperate calling threads
            if (this.InvokeRequired)
            {
                this.BeginInvoke(new ModbusTCP.Master.ExceptionData(MBmaster_OnException), new object[] { id, function, exception });
                return;
            }

            string exc = "PLC Error! Modbus: ";
            switch (exception)
            {
                case Master.excIllegalFunction: exc += "Illegal function!"; break;
                case Master.excIllegalDataAdr: exc += "Illegal data address!"; break;
                case Master.excIllegalDataVal: exc += "Illegal data value!"; break;
                case Master.excSlaveDeviceFailure: exc += "Slave device failure!"; break;
                case Master.excAck: exc += "Acknowledge!"; break;
                case Master.excSlaveIsBusy: exc += "Slave is busy!"; break;
                case Master.excGatePathUnavailable: exc += "Gateway path unavailbale!"; break;
                case Master.excExceptionTimeout: exc += "Slave timed out!"; break;
                case Master.excExceptionConnectionLost: exc += "Connection is lost!"; break;
                case Master.excExceptionNotConnected: exc += "Not connected!"; break;
            }
            log.Write(exc + "Modbus slave exception");
            CommEnabled = false;
            ModbusCommThreadOpen_b = false;
        }



        // ------------------------------------------------------------------------
        // Call watchdog reset every 500 ms
        // ------------------------------------------------------------------------
        private void Watchdog_Tick(object sender, System.EventArgs e)
        {
            MBmaster.WriteSingleRegister(0xFF, 0x1044, BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)0xc1)));
        }
    }

    class RequestToTx
    {
        public int Channel { get; set; }
        public Thompson.Tx.E_CommandTx Command { get; set; }
        public int ModulationRequest { get; set; }
        public int PowerRequest { get; set; }
        public bool GenPower { get; set; }
        public bool GenModulation { get; set; }
        public bool HeatOnForMinTime { get; set; }
    }
}
